package com.yoki.cloud.common.tenant;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import com.yoki.cloud.common.core.CurrentUser;
import com.yoki.cloud.common.core.GlobalConst;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class MybatisTenantInterceptor implements Interceptor {
    private static final String tag = "tenant";
    private Field additionalParametersField;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (!CurrentUser.isNeedTenantSQL()) {
            return invocation.proceed();
        }

        Object[] args = invocation.getArgs();
        if (args.length == 2) {// 根据参个数对照上面的注解可知是query还是update
            return update(invocation);
        } else {
            return query(invocation);
        }
    }

    private Object update(Invocation invocation) throws Exception {
        Object[] args = invocation.getArgs();
        Executor executor = (Executor) invocation.getTarget();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];

        BoundSql boundSql = ms.getBoundSql(parameter);

        String sql = TenantSQLUtil.processSQL(boundSql.getSql());

//        ReflectHelper.setValueByFieldName(boundSql, "sql", sql);
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql,
                removeTenantFlagTypeMapping(boundSql.getParameterMappings()),
                boundSql.getParameterObject());// 重新new一个查询语句对像

        // 当使用动态 SQL 时，可能会产生临时的参数，这些参数需要手动设置到新的 BoundSql 中
        Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
        for (String key : additionalParameters.keySet()) {
            newBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }
        newBoundSql.setAdditionalParameter(GlobalConst.TENANT_JAVA_FIELD_NAME, TenantSQLUtil.getTenantFlag());

        MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));// 把新的查询放到statement里

        return executor.update(newMs, parameter);
    }

    /**
     * 因为把租户标识直接处理到SQL上了，需要移除mybatis里的 多租户标识字段的映射，否则mybatis根据该映射去设参数会发现参数个数不匹配而抛异常
     *
     * @param parameterMappings
     * @return
     */
    private List<ParameterMapping> removeTenantFlagTypeMapping(List<ParameterMapping> parameterMappings) {
        if (parameterMappings == null) {
            return parameterMappings;
        }
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (GlobalConst.TENANT_JAVA_FIELD_NAME.equals(parameterMapping.getProperty())) {
                parameterMappings.remove(i);
                break;
            }
        }
        return parameterMappings;
    }

    private Object query(Invocation invocation) throws Exception {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        //由于逻辑关系，只会进入一次
        if (args.length == 4) {
            //4 个参数时
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }

        String sql = TenantSQLUtil.processSQL(boundSql.getSql());

//        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql,
//                boundSql.getParameterMappings(),
//                boundSql.getParameterObject());// 重新new一个查询语句对像
//        MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));// 把新的查询放到statement里
//        args[0] = newMs;

        ReflectHelper.setValueByFieldName(boundSql, "sql", sql);

        boundSql.setAdditionalParameter(GlobalConst.TENANT_JAVA_FIELD_NAME, TenantSQLUtil.getTenantFlag());

        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms,
                                                    SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(),
                ms.getId(), newSqlSource, ms.getSqlCommandType());


        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
// setStatementTimeout()
        builder.timeout(ms.getTimeout());


// setStatementResultMap()
        builder.parameterMap(ms.getParameterMap());


// setStatementResultMap()
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());


// setStatementCache()
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());


        return builder.build();
    }

    public static class BoundSqlSqlSource implements SqlSource {


        private BoundSql boundSql;


        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }


        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
        try {
            //反射获取 BoundSql 中的 additionalParameters 属性
            additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
            additionalParametersField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }
}
